from symbide import ModuleManager 
from symbide.src.parsing import parsers, PATTERN 
from parser import Parser
from xml.dom import minidom
from time import sleep

axes_attr = (('stride', '_stride'),
            ('tick_length', '_tick_length'),
            ('visible', 'visible'),
            ('overlay', '_overlay'),
            ('colored', '_colored'),
            ('label_axes', '_label_axes'),
            ('font_face', 'font_face'),
            ('label_ticks', '_label_ticks'),
            ('font_size', 'font_size')
            )

plotWin_attr = (('antialiasing', 'antialiasing'),
                ('ortho', 'ortho'),
                ('invert_mouse_zoom', 'invert_mouse_zoom'),
                ('caption', 'title'))
                
camera_attr = ('_dist', '_x', '_y')

class PlotParser(Parser):
    '''
    Creates proper DOM representation of a sympy.Plot object and
    does also the inverse.
    '''
    
    def plot_to_dom(self, plot):
        '''
        Returns a DOM representation of a sympy.Plot object.
        '''
        
        #Set up the basic DOM:
        DOM = minidom.Document()
        DOM_plot = minidom.Element('symbide_plot')
        DOM_plot.setAttribute('path','')
        
        DOM_functions = minidom.Element('plot_functions')
        DOM_startArgs = minidom.Element('plot_startArgs')
        DOM_camera = minidom.Element('plot_camera')
        DOM_plot.appendChild(DOM_functions)
        DOM_plot.appendChild(DOM_startArgs)
        DOM_plot.appendChild(DOM_camera)
        
        #extract all functions
        for f in plot._functions.values():
            textnode = minidom.Document().createTextNode(str([f]))
            DOM_func = minidom.Element('plot_function')
            DOM_func.appendChild(textnode)
            DOM_functions.appendChild(DOM_func)
            
        dic_startArgs={}
        dic_camera={}
        
        #gets all initializing arguments that are still available
        dic_startArgs.update(plot._win_args)
        
        #get all axes attributes
        buf = []
        for a in axes_attr:
            exec '''buf.append("%s = "+str(plot.axes.%s))'''%a
        
        #the axes attributes can be added to the initializing arguments
        dic_startArgs['axes']=';'.join(buf)
            
        #if the window has been opened we have to get this values too
        if plot._window:
            #get all necessary values of the plot-window
            for a in plotWin_attr:
                exec 'dic_startArgs["%s"]=plot._window.%s'%a
            dic_startArgs.update(plot._window.win_args)
            
            #the camera is very hard accessable, but important
            if plot._window.camera:
                for a in camera_attr:
                    exec 'dic_camera["%s"]=plot._window.camera.%s'%(a, a)
                dic_camera['rot']=tuple(plot._window.camera._rot)
        
        #finish tho DOM
        startArgs = DOM.createTextNode("%s"%dic_startArgs)
        DOM_startArgs.appendChild(startArgs)
        camera = DOM.createTextNode("%s"%dic_camera)
        DOM_camera.appendChild(camera)
        
        return DOM_plot
    
    def plot_to_xml(self, plot):
        '''
        Returns a xml representation of the plot object.
        '''
        
        return self.plot_to_dom(plot).toxml()
    
    def dom_to_plot(self, DOM_plot, interactive=True):
        '''
        Creates the plot-object that is defined by the DOM. If interactive
        is true, the plot window is opened immediately.
        Attention: If the window is not opened the camera can't be set!
        TODO: enable camera setting without opening window
        '''
        
        if not ModuleManager.hasModule('ctypes') or not ModuleManager.hasModule('sympy'):
            return
        
        from ctypes import c_float
        import sympy
        
        DOM_functions = DOM_plot.getElementsByTagName('plot_functions')[0]
        DOM_startArgs = DOM_plot.getElementsByTagName('plot_startArgs')[0]
        DOM_camera = DOM_plot.getElementsByTagName('plot_camera')[0]
        
        #get all initializing arguments
        exec 'dic_startArgs='+DOM_startArgs.childNodes[0].nodeValue.strip(' \n\t')
        
        #TODO: set start arguments
        dic_startArgs = {}
        
        dic_startArgs['visible']=interactive
        
        #create the plot object
        namespace = {'__sympyPlot__' : sympy.Plot(**dic_startArgs)}
        sympyNamespace = vars(sympy)
        
        #set the functions
        for f in DOM_functions.getElementsByTagName('plot_function'):
            text = f.childNodes[0].nodeValue
            text, symbDef = self._parse(text)
            for i in symbDef:
                exec i in sympyNamespace, namespace
            exec '__sympyPlot__.append(*%s)'%text in sympyNamespace, namespace
        
        #if no window should be opened, exit now
        if not interactive:
            return namespace['__sympyPlot__']
        
        #get the camera values
        exec 'dic_camera='+DOM_camera.childNodes[0].nodeValue.strip(' \n\t')
        __sympyPlot__ = namespace['__sympyPlot__']
        
        #we have to wait until the camera is created
        while True:
            try:
                if __sympyPlot__._window.has_exit:
                    return __sympyPlot__
                if __sympyPlot__._window.camera:
                    break
            except:
                sleep(0.5)
        
        #now the plotting window has to wait
        __sympyPlot__._render_lock.acquire()
        
        #set the camera
        for i in dic_camera:
            if i in camera_attr:
                exec '__sympyPlot__._window.camera.%s = dic_camera["%s"]'%(i,i)
        if 'rot' in dic_camera:
            array =  c_float*16
            m = ('%s,'*15+'%s')%dic_camera['rot']
            exec 'rot = array(%s)'%m
            __sympyPlot__._window.camera._rot = rot
            
        #give control back to plotting window
        __sympyPlot__._render_lock.release()        
        
        return __sympyPlot__
    
    def _parse(self, text):
        '''
        Returns a ready to execute string plus the Symbol Definitions.
        
        In the text all numbers are enclosed by sympy.Number()
        '''
        
        text = parsers['number'].parse(text)
        symbDef = self._getSymbolDefinition(text)
        return text, symbDef
        
    def _getSymbolDefinition(self, text):
        if not ModuleManager.hasModule('ctypes') or not ModuleManager.hasModule('sympy'):
            return
        import sympy
        buf = text[:text.rfind(',')]
        buf = PATTERN['variable'].findall(buf)
        symbDef = []
        key = dir(sympy)
        for s in buf:
            if s not in symbDef and s not in key:
                symbDef.append(s)
        return ['%s = Symbol("%s")'%(s,s) for s in symbDef]
    
    